﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.IO;
using System.Reflection;
using System.CodeDom;
using System.CodeDom.Compiler;

namespace Milo.Builder
{
    public class CodeGen
    {
        private Dictionary<Type, ClassBuilder> typeBuilders = new Dictionary<Type,ClassBuilder>();

		/// <summary>
		/// Initializes a new instance of the <see cref="CodeGen"/> class.
		/// </summary>
		public CodeGen()
		{
			AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += new ResolveEventHandler(CurrentDomain_ReflectionOnlyAssemblyResolve);
		}

		/// <summary>
		/// Handles the ReflectionOnlyAssemblyResolve event of the CurrentDomain control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="args">The <see cref="System.ResolveEventArgs"/> instance containing the event data.</param>
		/// <returns></returns>
		protected Assembly CurrentDomain_ReflectionOnlyAssemblyResolve(object sender, ResolveEventArgs args)
		{
			Assembly assembly = GetReflectionOnlyLoadedAssembly(new AssemblyName(args.Name));

			if (assembly == null)
			{
				//Attempt to load from the known locations.  
				try
				{
					assembly = Assembly.ReflectionOnlyLoad(args.Name);
				}
				catch (FileNotFoundException)
				{
					//we can't find the assembly by normal means so derive the dll name from the assembly and look for it
					//in the original assembly directory
					string[] s = args.Name.Split(new string[] { ", " }, StringSplitOptions.RemoveEmptyEntries);

					assembly = Assembly.ReflectionOnlyLoadFrom(assemblyDir + Path.DirectorySeparatorChar + s[0] + ".dll");
				}
				if (assembly == null)
					throw new Milo.Builder.BuilderException("Cannot find assembly " + args.Name + " when trying to resolve dependencies");
			}

			return assembly; 
		}

		protected Assembly GetReflectionOnlyLoadedAssembly(AssemblyName assemblyName)
		{
			Assembly[] loadedAssemblies = AppDomain.CurrentDomain.ReflectionOnlyGetAssemblies(); 
			foreach (Assembly assembly in loadedAssemblies) 
				if (assemblyName.FullName == assembly.FullName)
					return assembly;

			return null;
		}

		private string assemblyDir;

		public void AddDllFilename(string dll)
        {
            //load file requires an absolute path
            FileInfo fi = new FileInfo(dll);

			assemblyDir = fi.DirectoryName;

            AddAssembly(Assembly.ReflectionOnlyLoadFrom(fi.FullName));
        }

        public void AddAssemblies(string assemblies)
        {
        }

        public void AddAssembly(string assembly)
        {
        }

        public void AddAssembly(Assembly assembly)
        {
            foreach (Type type in assembly.GetTypes())
				if (type.IsPublic && type.IsClass && !type.IsAbstract && !typeof(Exception).IsAssignableFrom(type))
                    AddClass(type);
        }

        public void AddClass(Type type)
        {
            if (!type.IsClass)
                throw new BuilderException(type.ToString() + " is not a class");

            if (!typeBuilders.ContainsKey(type))
            {
                ClassBuilder builder = new ClassBuilder(type);

                typeBuilders.Add(type, builder);

                foreach (Type childType in builder.GetChildTypes())
                    AddClass(childType);
            }
        }

        public Type[] GetMappedClasses()
        {
            Type[] types = new Type[typeBuilders.Count];

            int i = 0;

            foreach (KeyValuePair<Type, ClassBuilder> kvp in typeBuilders)
                types[i++] = kvp.Key;

            return types;
        }

        public CodeCompileUnit GenerateCode()
        {
            CodeCompileUnit unit = new CodeCompileUnit();

            CodeNamespaceImport importSystem = new CodeNamespaceImport("System");
            CodeNamespaceImport importSystemCollection = new CodeNamespaceImport("System.Collections.Generic");
            CodeNamespaceImport importMilo = new CodeNamespaceImport("Milo.Framework");

            // divi the classes to generate into their namespaces
            Dictionary<string, CodeNamespace> namespaces = new Dictionary<string, CodeNamespace>();

            //get the code dom for each class (in it's correct namespace
            foreach (KeyValuePair<Type, ClassBuilder> kvp in typeBuilders)
            {
                string xx = kvp.Key.Namespace + ".Controls";
                CodeNamespace ns;
                if (namespaces.ContainsKey(xx))
                    ns = namespaces[xx];
                else
                {
                    namespaces[xx] = ns = new CodeNamespace(xx);
                    ns.Imports.Add(importSystem);
                    ns.Imports.Add(importSystemCollection);
                    ns.Imports.Add(importMilo);
                }

                ns.Types.Add(kvp.Value.GenerateClassCode());
            }

            //sort the namespaces and pop in the unit
            //namespaces.OrderBy((first, second) => { first.Key.CompareTo(second.Key); });
            foreach (KeyValuePair<string, CodeNamespace> kvp in namespaces.OrderBy(key=>key.Key))
                unit.Namespaces.Add(kvp.Value);

            return unit;

        }

        public void GenerateSource(TextWriter writer)
        {
            CodeDomProvider provider = new Microsoft.CSharp.CSharpCodeProvider();

            provider.GenerateCodeFromCompileUnit(GenerateCode(), writer, new CodeGeneratorOptions());
        }
    }
}